<?php
/**
 * Front Controller Plugin
 *
 * @package     Visi
 * @subpackage  Controller Plugins
 * @author      Luke Visinoni <luke.visinoni@gmail.com>
 * @copyright   (c) 2011 Luke Visinoni <luke.visinoni@gmail.com>
 * @version     $Id$
 */
class Visi_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract
{
    /**
     * @var Zend_Acl
     **/
    protected $_acl;

    /**
     * @var string
     **/
    protected $_roleName;

    /**
     * @var array
     **/
    protected $_errorPage;

    /**
     * Constructor
     *
     * @param mixed $aclData
     * @param $roleName
     * @return void
     **/
    public function __construct(Zend_Acl $aclData, $roleName = 'defaultRole')
    {
        $this->_errorPage = array('module' => Zend_Controller_Front::getInstance()->getDefaultModule(), 
                                  'controller' => 'error', 
                                  'action' => 'denied');

        $this->_roleName = $roleName;

        if (null !== $aclData) {
            $this->setAcl($aclData);
        }
    }

    /**
     * Sets the ACL object
     *
     * @param mixed $aclData
     * @return void
     **/
    public function setAcl(Zend_Acl $aclData)
    {
        $this->_acl = $aclData;
    }

    /**
     * Returns the ACL object
     *
     * @return Zend_Acl
     **/
    public function getAcl()
    {
        return $this->_acl;
    }

    /**
     * Sets the ACL role to use
     *
     * @param string $roleName
     * @return void
     **/
    public function setRoleName($roleName)
    {
        $this->_roleName = $roleName;
    }

    /**
     * Returns the ACL role used
     *
     * @return string
     * @author 
     **/
    public function getRoleName()
    {
        return $this->_roleName;
    }

    /**
     * Sets the error page
     *
     * @param string $action
     * @param string $controller
     * @param string $module
     * @return void
     * @todo Maybe this should keep its current values rather than set defaults
     *       if not all params were passed in.
     *       Or maybe instead there should be individual setters and getters for
     *       these parameters.
     **/
    public function setErrorPage($action = null, $controller = null, $module = null)
    {
        if (is_null($action)) $action = 'denied';
        if (is_null($controller)) $controller = 'error';
        if (is_null($module)) $module = Zend_Controller_Front::getInstance()->getDefaultModule();
        $this->_errorPage = array('module' => $module, 
                                  'controller' => $controller,
                                  'action' => $action);
    }

    /**
     * Returns the error page
     *
     * @return array
     **/
    public function getErrorPage()
    {
        return $this->_errorPage;
    }

    /**
     * Predispatch
     * Checks if the current user identified by roleName has rights to the requested url (module/controller/action)
     * If not, it will call denyAccess to be redirected to errorPage
     *
     * @return void
     **/
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        // set the error page for the acl plugin
        $this->setErrorPage(null, null, $request->getModuleName());
        
        $resourceName = '';
        
        // @todo sucks that the module name is hard-coded
        if ($request->getModuleName() != 'frontend') {
            $resourceName .= $request->getModuleName() . ':';
        }
        
        $resourceName .= $request->getControllerName();
        
        /** Check if the controller/action can be accessed by the current user */
        if (!$this->getAcl()->isAllowed($this->_roleName, $resourceName, $request->getActionName())) {
            /** Redirect to access denied page */
            $this->denyAccess($request);
        }
    }

    /**
     * Deny Access Function
     * Redirects to errorPage, this can be called from an action using the action helper
     *
     * @return void
     **/
    public function denyAccess(Zend_Controller_Request_Abstract $request)
    {
        // only apply the "next" param if user isn't logged in
        if (!Zend_Auth::getInstance()->hasIdentity()) {
            $router = Zend_Controller_Front::getInstance()->getRouter();
            $params = array(
                'module' => $request->getModuleName(),
                'controller' => $request->getControllerName(),
                'action' => $request->getActionName(),
            );
            $params = array_merge($params, $request->getParams());
            $url = $router->assemble($params);
            $this->_request->setParam('next', $url);
        }
        $this->_request->setModuleName($this->_errorPage['module']);
        $this->_request->setControllerName($this->_errorPage['controller']);
        $this->_request->setActionName($this->_errorPage['action']);
    }
}
